local skynet = require "skynet"
local SocketHelper = require "http.sockethelper"
local Socket = require "skynet.socket"
local Httpd = require "http.httpd"
local Module = require "core.Module"
local Json = require "lualib.Json"
local MD5 = require "md5"
local API_CODE = require "enum.APICode"

local string = string
local table = table
local tostring = tostring

--[[
后台 api 网关，处理从 admin 过来的请求
]]

---@class ApiGate : Service
local M = {}
local handler = {}

local protocol
local logChannel = "apigate"
local apiKey

local function checkSign(params, sign)
    local keys = table.keys(params)
    table.sort(keys)
    local signTbl = {}
    for i = 1, #keys do
        local key = keys[i]
        if key ~= "sign" and key ~= "method" then
            table.insert(signTbl, string.format("%s=%s", key, params[key]))
        end
    end

    table.insert(signTbl, "method=" .. params.sign)
    table.insert(signTbl, apiKey)
    local t_sign = MD5.sumhexa(table.concat(signTbl, "&"))
    return t_sign == params.sign
end

local function response(id, write, statuscode, bodyfunc, header)
    local ok, err = Httpd.write_response(write, statuscode, bodyfunc, header)
    if not ok then
        -- if err == sockethelper.socket_error , that means socket closed.
        LERRF(string.format("respose error, fd = %d, err: %s", id, err))
    end
end

local SSLCTX_SERVER = nil
local function genInterface(protocol, fd)
    if protocol == "http" then
        return {
            init = nil,
            close = nil,
            read = SocketHelper.readfunc(fd),
            write = SocketHelper.writefunc(fd)
        }
    elseif protocol == "https" then
        local tls = require "http.tlshelper"
        if not SSLCTX_SERVER then
            SSLCTX_SERVER = tls.newctx()
            -- gen cert and key
            -- openssl req -x509 -newkey rsa:2048 -days 3650 -nodes -keyout server-key.pem -out server-cert.pem
            local certfile = skynet.getenv("certfile") or "./server-cert.pem"
            local keyfile = skynet.getenv("keyfile") or "./server-key.pem"
            SSLCTX_SERVER:set_cert(certfile, keyfile)
        end

        local tls_ctx = tls.newtls("server", SSLCTX_SERVER)
        return {
            init = tls.init_responsefunc(fd, tls_ctx),
            close = tls.closefunc(tls_ctx),
            read = tls.readfunc(fd, tls_ctx),
            write = tls.writefunc(fd, tls_ctx)
        }
    else
        error(string.format("Invalid protocol: %s", protocol))
    end
end

local function closeFd(interface, fd, code)
    if code then
        response(fd, interface.write, {
            code = code
        })
    end

    Socket.close(fd)
    if interface.close then
        interface.close()
    end
end

-- 处理请求
function M.handle(id)
    Socket.start(id)
    local interface = genInterface(protocol, id)
    if interface.init then
        interface.init()
    end

    -- 限制一次请求最多 8192(8K) 字节
    local code, url, method, header, body = Httpd.read_request(interface.read, 8192)
    assert(url == nil or url == '/' or url == "", "apigate has use route, url: " .. tostring(url))
    assert(method:upper() == "POST", "request method is not POST, is " .. tostring(method))
    if not code then
        if url == SocketHelper.socket_error then
            LERR(logChannel, "read_request error, socket closed")
            closeFd(interface, id, API_CODE.REQUEST_ERROR1)
        else
            LERR(logChannel, "read_request error, socket closed" .. tostring(url))
            closeFd(interface, id, API_CODE.REQUEST_ERROR2)
        end
        return
    end

    if code ~= 200 then
        -- code >= 100
        closeFd(interface, id, code)
        return
    end

    local params = Json.decode(body)
    if not params.sign then
        LERR(logChannel, code)
        closeFd(interface, id, API_CODE.SIGN_ERROR)
        return
    end

    if not checkSign(params) then
        LERR(logChannel, "sign error, params: " .. table.dump(params))
        closeFd(interface, id, API_CODE.SIGN_ERROR)
        return
    end

    local f = handler[params.method]
    if not f then
        LERR(logChannel, "has not method: " .. tostring(params.method))
        closeFd(interface, id, API_CODE.METHOD_NOT_EXISTS)
        return
    end

    local ok, ret = pcall(f, params)
    if not ok then
        LERRF(logChannel, "handler error, params: %s, ret: %s", table.dump(params), ret)
        closeFd(interface, id, API_CODE.HANDLER_ERROR)
        return
    end

    response(id, interface.write, code, Json.encode(ret))
    closeFd(interface, id)
end

local function initHandler(mod)
    -- 初始化协议处理器
    local ok, apiHandler = pcall(require, Module.APIPath(mod, true))
    if not ok then
        -- 没有协议处理器
        return
    end

    for method, func in pairs(apiHandler) do
        assert(not handler[method], "duplicate proto key: " .. method)
        handler[method] = func
    end
end

function M.start(conf)
    apiKey = conf.key or ""
    protocol = conf.protocol

    table.foreach(Module.allAPIMod(), initHandler)
end

function M.init()
    skynet.dispatch_lua(M)
end

return M